package org.proteored.miapeapi.xml.mzidentml;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.proteored.miapeapi.cv.Accession;
import org.proteored.miapeapi.cv.ControlVocabularyManager;
import org.proteored.miapeapi.cv.ControlVocabularyTerm;
import org.proteored.miapeapi.cv.ms.RetentionTime;
import org.proteored.miapeapi.exceptions.IllegalMiapeArgumentException;
import org.proteored.miapeapi.interfaces.msi.IdentifiedPeptide;
import org.proteored.miapeapi.interfaces.msi.IdentifiedProtein;
import org.proteored.miapeapi.interfaces.msi.InputData;
import org.proteored.miapeapi.interfaces.msi.PeptideScore;
import org.proteored.miapeapi.xml.mzidentml.autogenerated.FuGECommonOntologyCvParamType;
import org.proteored.miapeapi.xml.mzidentml.autogenerated.FuGECommonOntologyParamType;
import org.proteored.miapeapi.xml.mzidentml.autogenerated.PSIPIAnalysisSearchSpectrumIdentificationItemType;
import org.proteored.miapeapi.xml.mzidentml.autogenerated.PSIPIAnalysisSearchSpectrumIdentificationResultType;
import org.proteored.miapeapi.xml.mzidentml.autogenerated.PSIPIMainMzIdentMLType;
import org.proteored.miapeapi.xml.mzidentml.autogenerated.PSIPIPolypeptidePeptideType;
import org.proteored.miapeapi.xml.mzidentml.autogenerated.PSIPISpectraSpectraDataType;
import org.proteored.miapeapi.xml.util.MiapeXmlUtil;
import org.proteored.miapeapi.xml.util.parallel.MapSync;

import pi.ParIterator;
import pi.reductions.Reducible;

public class SpectrumIdentificationResultParallelProcesor extends Thread {
	private static Logger log = Logger.getLogger("log4j.logger.org.proteored");

	private final ParIterator<PSIPIAnalysisSearchSpectrumIdentificationResultType> iterator;

	private final int numThread;

	private final ControlVocabularyManager cvManager;
	private final PSIPIMainMzIdentMLType mzIdentML;
	private final MapSync<String, InputData> syncInputDataHash;
	private final Reducible<List<IdentifiedPeptide>> reduciblePeptides;
	private final Reducible<Map<String, IdentifiedProtein>> reducibleProteinHash;

	public SpectrumIdentificationResultParallelProcesor(
			ParIterator<PSIPIAnalysisSearchSpectrumIdentificationResultType> iterator,
			int numCore, ControlVocabularyManager cvManager,
			PSIPIMainMzIdentMLType mzIdentML,
			MapSync<String, InputData> syncInputDataHash,
			Reducible<List<IdentifiedPeptide>> reduciblePeptides,
			Reducible<Map<String, IdentifiedProtein>> reducibleProteinHash) {
		this.iterator = iterator;
		numThread = numCore;
		this.cvManager = cvManager;
		this.syncInputDataHash = syncInputDataHash;
		this.reduciblePeptides = reduciblePeptides;
		this.mzIdentML = mzIdentML;
		this.reducibleProteinHash = reducibleProteinHash;

	}

	@Override
	public void run() {

		List<IdentifiedPeptide> peptides = new ArrayList<IdentifiedPeptide>();
		reduciblePeptides.set(peptides);
		Map<String, IdentifiedProtein> proteinHash = new HashMap<String, IdentifiedProtein>();
		reducibleProteinHash.set(proteinHash);

		while (iterator.hasNext()) {
			try {
				final PSIPIAnalysisSearchSpectrumIdentificationResultType spectIdentResultXML = iterator
						.next();

				// TODO this is very important:!
				final String spectrumID = spectIdentResultXML.getSpectrumID();
				String spectrumRef = getSpectrumRef(spectrumID);

				// Retention time
				String RT = getRetentionTimeInSeconds(spectIdentResultXML);

				PSIPISpectraSpectraDataType spectraDataXML = getSpectraData(
						spectIdentResultXML.getSpectraDataRef(), mzIdentML
								.getDataCollection().getInputs()
								.getSpectraData());

				// Input data
				// check if the spectraData is already captured. If not, add a
				// new input Data
				if (!syncInputDataHash.containsKey(spectraDataXML.getId())) {
					Integer inputDataID = MiapeXmlUtil.InputDataCounter
							.increaseCounter();
					InputData inputData = new InputDataImpl(spectraDataXML,
							inputDataID);
					syncInputDataHash.put(spectraDataXML.getId(), inputData);
				}
				InputData inputData = syncInputDataHash.get(spectraDataXML
						.getId());

				// log.debug(spectIdentResultXML.getSpectrumIdentificationItem().size()
				// + " SpectrumIdentificationItems");
				Set<PeptideScore> scoresFromFirstPeptide = new HashSet<PeptideScore>();

				List<PSIPIAnalysisSearchSpectrumIdentificationItemType> spectrumIdentificationItems = spectIdentResultXML
						.getSpectrumIdentificationItem();
				for (PSIPIAnalysisSearchSpectrumIdentificationItemType spectIdentItemXML : spectrumIdentificationItems) {

					// some peptides contribute to proteins even if they
					// have
					// not passed the threshold
					// if (spectIdentItemXML.isPassThreshold()) {

					PSIPIPolypeptidePeptideType peptideXML = getPeptide(
							spectIdentItemXML.getPeptideRef(), mzIdentML
									.getSequenceCollection().getPeptide());

					boolean includePeptide = false;
					final Set<PeptideScore> scores = IdentifiedPeptideImpl
							.getScoresFromThisPeptides(spectIdentItemXML,
									peptideXML, cvManager);
					if (scores == null || scores.isEmpty()) {
						log.debug("Skipping SII:" + spectIdentItemXML.getId()
								+ " because no scores have found");
						continue;
					}

					if (spectIdentItemXML.getRank() == 1) {
						includePeptide = true;
						scoresFromFirstPeptide.clear();
						scoresFromFirstPeptide.addAll(scores);
					} else {
						// if the rank > 1 has the same scores, also
						// include it
						if (comparePeptideScores(scoresFromFirstPeptide, scores) == 0) {
							includePeptide = true;
							log.debug("Peptide with rank "
									+ spectIdentItemXML.getRank()
									+ " is going to be included");
						}
					}
					if (!includePeptide) {
						break;
					} else {
						// CREATE Peptide
						Integer peptideID = MiapeXmlUtil.PeptideCounter
								.increaseCounter();
						IdentifiedPeptide peptide = new IdentifiedPeptideImpl(
								spectIdentItemXML, peptideXML, mzIdentML,
								inputData, spectrumRef, peptideID, cvManager,
								proteinHash, RT);
						// if the peptide has no scores, not report it
						if (peptide.getScores() == null
								|| peptide.getScores().isEmpty())
							throw new IllegalMiapeArgumentException(
									"The peptide from SII:"
											+ spectIdentItemXML.getId()
											+ " has no scores!");

						// Add the peptide to the peptide list
						peptides.add(peptide);

					}
				}
			} catch (Exception e) {
				iterator.register(e);
			}
		}
		log.debug("Thread " + numThread + " processed " + peptides.size()
				+ " peptides and " + proteinHash.size() + " proteins");
	}

	/**
	 * Returns the spectrumRef of the spectrumIdentificationResult
	 * 
	 * @param spectrumID
	 *            : If the mzIdentML comes from a MASCOT mgf search, the
	 *            spectrumID should be "index=x" where x is the order of the
	 *            spectra in the MGF (starting by 0) If the mzIdentML comes from
	 *            a MASCCOT mzML search, the spectrumID should be
	 *            "mzMLid=controllerType=0 controllerNumber=1 scan=3423" where
	 *            the scan is the order of the spectra in the mzML (starting by
	 *            1)
	 * @return
	 */
	private String getSpectrumRef(String spectrumID) {
		if (spectrumID == null)
			return null;

		String mzMLSpectrumIDRegexp = "^mzMLid=(.*)$";

		if (Pattern.matches(mzMLSpectrumIDRegexp, spectrumID)) {
			// in case of being a spectrumID of a mzML file, return the
			// string that appears after the "mzMLid="
			Pattern p = Pattern.compile(mzMLSpectrumIDRegexp);
			Matcher m = p.matcher(spectrumID);
			if (m.find())
				return m.group(1);

		}
		Integer specRefInt = 0;
		String mgfSpectrumIDRegexp = "^index=(\\d+)$";
		String mzMLSpectrumIDRegexp2 = ".*scan=(\\d+)$";
		String mgfSpectrumIDRegexpQuery = "^query=(\\d+)$";
		String mgfSpectrumIDRegexpQuery2 = "^(\\d+)$";

		// if is like "index=1235"
		if (Pattern.matches(mgfSpectrumIDRegexp, spectrumID)) {
			Pattern p = Pattern.compile(mgfSpectrumIDRegexp);
			Matcher m = p.matcher(spectrumID);
			if (m.find()) {
				specRefInt = Integer.valueOf(m.group(1)) + 1; // sum 1 (starts
																// by 0)
			}
			// if it is like
			// "mzMLid=controllerType=0 controllerNumber=1 scan=3423"
		} else if (Pattern.matches(mzMLSpectrumIDRegexp2, spectrumID)) {
			Pattern p = Pattern.compile(mzMLSpectrumIDRegexp2);
			Matcher m = p.matcher(spectrumID);
			if (m.find()) {
				specRefInt = Integer.valueOf(m.group(1)); // don't sum 1 (starts
															// by 1)
			}
		} else if (Pattern.matches(mgfSpectrumIDRegexpQuery, spectrumID)) {
			Pattern p = Pattern.compile(mgfSpectrumIDRegexpQuery);
			Matcher m = p.matcher(spectrumID);
			if (m.find()) {
				specRefInt = Integer.valueOf(m.group(1));
			}
		} else if (Pattern.matches(mgfSpectrumIDRegexpQuery2, spectrumID)) {
			Pattern p = Pattern.compile(mgfSpectrumIDRegexpQuery2);
			Matcher m = p.matcher(spectrumID);
			if (m.find()) {
				specRefInt = Integer.valueOf(m.group(1));
			}
		}
		if (specRefInt != 0)
			return specRefInt.toString();
		return null;
	}

	private int comparePeptideScores(Set<PeptideScore> scoresFromFirstPeptide,
			Set<PeptideScore> scores) {
		if (scoresFromFirstPeptide != null && scores != null) {
			if (scores.size() == scoresFromFirstPeptide.size()) {
				for (PeptideScore peptideScore : scores) {
					if (!foundPeptideScore(peptideScore, scoresFromFirstPeptide))
						return -1;
				}
				return 0;
			}
		}
		return -1;
	}

	private boolean foundPeptideScore(PeptideScore peptideScore,
			Set<PeptideScore> scoresFromFirstPeptide) {
		for (PeptideScore peptideScore2 : scoresFromFirstPeptide) {
			if (peptideScore2.getName().equals(peptideScore.getName()))
				if (peptideScore2.getValue().equals(peptideScore.getValue()))
					return true;
		}
		return false;
	}

	private String getRetentionTimeInSeconds(
			PSIPIAnalysisSearchSpectrumIdentificationResultType spectIdentResultXML) {
		if (spectIdentResultXML != null) {
			if (spectIdentResultXML.getParamGroup() != null) {
				for (FuGECommonOntologyParamType paramType : spectIdentResultXML
						.getParamGroup()) {
					if (paramType instanceof FuGECommonOntologyCvParamType) {
						FuGECommonOntologyCvParamType cvparam = (FuGECommonOntologyCvParamType) paramType;
						ControlVocabularyTerm cvTerm = RetentionTime
								.getInstance(cvManager).getCVTermByAccession(
										new Accession(cvparam.getAccession()));
						if (cvTerm != null) {
							if (cvparam.getValue() != null) {
								try {
									int num = Integer.valueOf(cvparam
											.getValue());
									// check unit
									String unitAccession = cvparam
											.getUnitAccession();
									if (unitAccession != null) {
										if ("UO:0000010".equals(unitAccession)) {
											log.debug("Retention time in seconds: "
													+ num);
										} else if ("UO:0000031"
												.equals(unitAccession)) {
											log.debug("Retention time in minutes: "
													+ num);
											num = num * 60;
											log.debug("Retention time converted to seconds: "
													+ num);
										}
									}
									return String.valueOf(num);
								} catch (NumberFormatException e) {

								}
							}
						}
					}
				}
			}
		}
		return null;
	}

	private PSIPISpectraSpectraDataType getSpectraData(String spectraDataRef,
			List<PSIPISpectraSpectraDataType> spectraDatas) {
		for (PSIPISpectraSpectraDataType spectraData : spectraDatas) {
			if (spectraData.getId().equals(spectraDataRef)) {
				return spectraData;
			}
		}
		return null;
	}

	private PSIPIPolypeptidePeptideType getPeptide(String peptideRef,
			List<PSIPIPolypeptidePeptideType> peptides) {
		for (PSIPIPolypeptidePeptideType polypeptideXML : peptides) {
			if (polypeptideXML.getId().equals(peptideRef)) {
				return polypeptideXML;
			}
		}
		return null;
	}
}
